<style lang="scss">
  .ua-lazy-audio {
    display: inline-block !important;
    position: relative;
    box-sizing: border-box;
    overflow: hidden;
    line-height: 1;
    &-border {
      border: 1px dashed #e2e2e2;
    }
    input[type='file'] {
      display: inline-block;
      width: 100%;
      height: 100%;
      position: absolute;
      top: 0;
      left: 0;
      opacity: 0;
      cursor: pointer;
    }
    &--poster {
      width: 100%;
      height: 100%;
      overflow: hidden;
      z-index: 0;
      position: relative;
    }
    &--progress {
      width: 100%;
      height: 0;
      position: absolute;
      left: 0;
      bottom: 0;
      opacity: 0;
      transition: height 0.5s ease 0.2s, opacity 0.3s ease 0.3s;
      &-thumb {
        position: absolute;
        top: 0;
        left: -100%;
        width: 100%;
        height: 100%;
      }
    }
    &:hover &--progress {
      height: 7px;
      opacity: 0.6;
      &:hover {
        height: 15px;
        opacity: 1;
        cursor: pointer;
      }
    }
    &--tool {
      z-index: 2;
      cursor: pointer;
      @include translate-center(both);
    }
  }
</style>

<template>
  <div class="ua-lazy-audio" :class="{ 'ua-lazy-audio-border': border }" :style="boxStyle">
    <audio ref="audioElem" @timeupdate="onTimeupdate" @ended="onEnded" @canplay="onCanplay" @error="onError" />
    <div class="ua-lazy-audio--poster" v-if="!noPoster">
      <UImage :src="poster" :width="width" :height="height">
        <template #error>
          <UIconfont name="image-error" :size="imgErrorSize" color="#f5f7fa" />
        </template>
      </UImage>
    </div>
    <div
      v-if="played"
      class="ua-lazy-audio--progress"
      ref="progressElem"
      :style="{ backgroundColor: status > -1 ? '#f2f2f2' : '' }"
      @mousedown="onMousedown"
    >
      <div
        class="ua-lazy-audio--progress-thumb"
        :style="{
          transform: `translate3d(${played}%, 0, 0)`,
          backgroundColor: playIconColor,
        }"
      />
    </div>
    <div class="ua-lazy-audio--tool">
      <UIconfont :size="playIconSize" :name="iconName" :cursor="iconCursor" :color="iconColor" @click="onPlay" />
    </div>
  </div>
</template>

<script lang="ts">
  import { qiniuFullLink } from '@assets/utils/qiniu'
  import { fileRead } from '@assets/utils/util'
  import { computed, defineComponent, onMounted, Ref, ref, unref, watch } from 'vue'
  import UImage from './UImage.vue'
  import UIconfont from './UIconfont.vue'

  const enum AudioStatus {
    /** 未赋值 src */
    NOT_LOAD = 0,
    /** 资源加载失败 */
    ERROR = 1,
    /** 暂停 */
    PAUSED = 2,
    /** 播放 */
    PLAYED = 3,
  }

  export default defineComponent({
    name: 'UAudioLazy',
    components: { UImage, UIconfont },
    props: {
      src: { type: [String, File], default: '' },
      poster: { type: [String, File], default: '' },
      noPoster: { type: Boolean, default: false },
      border: { type: Boolean, default: true },
      width: {
        type: Number,
        default: 50,
      },
      height: {
        type: Number,
        default: 50,
      },
      playIconSize: {
        type: Number,
        default: 25,
      },
      playIconColor: {
        type: String,
        default: '#009688',
      },
      lazy: {
        type: Boolean,
        default: true,
      },
    },
    setup(props) {
      let mousedown = false
      const unit = import.meta.env.VITE_LENGTH_UNIT
      /** 播放状态 */
      const status = ref(AudioStatus.NOT_LOAD)
      /** 播放进度 */
      const played = ref<number | string>(0)
      /** <audio> */
      const audioElem = ref<any>(null) as Ref<HTMLMediaElement>
      const progressElem = ref<any>(null) as Ref<HTMLDivElement>
      /** 已将资源赋值给 <audio>，与 props.lazy 配合 */
      const loaded = ref(false)
      const iconCursor = computed(() => {
        // 不是懒加载 或 懒加载但已赋值
        if (!props.lazy || loaded.value) {
          switch (status.value) {
            case AudioStatus.ERROR:
              return 'not-allowed'
            case AudioStatus.NOT_LOAD:
              return 'auto'
            default:
              return 'pointer'
          }
        }
        // 懒加载 & 未赋值
        return props.src ? 'pointer' : 'auto'
      })
      const iconName = computed(() => {
        // 不是懒加载 或 懒加载但已赋值
        if (!props.lazy || loaded.value) {
          switch (status.value) {
            case AudioStatus.PLAYED:
              return 'stop'
            case AudioStatus.PAUSED:
              return 'play'
            default:
              return 'music'
          }
        }
        // 懒加载 & 未赋值
        return props.src ? 'play' : 'music'
      })
      const iconColor = computed(() => {
        // 不是懒加载 或 懒加载但已赋值
        if (!props.lazy || loaded.value) {
          switch (status.value) {
            case AudioStatus.ERROR:
              return '#F56C6C'
            case AudioStatus.NOT_LOAD:
              return '#e2e2e2'
            default:
              return props.playIconColor
          }
        }
        // 懒加载 & 未赋值
        return props.src ? props.playIconColor : '#e2e2e2'
      })

      const boxStyle = computed(() => ({
        width: `${props.width}${unit}`,
        height: `${props.height}${unit}`,
        borderColor: iconColor.value,
      }))

      const imgErrorSize = computed(() => Math.min(props.width ?? 0, props.height ?? 0) * 0.4)

      // 资源卸载
      function unload() {
        const elem = unref(audioElem)
        if (!elem.paused) {
          elem.pause()
        }
        elem.src = ''
        loaded.value = false
        status.value = AudioStatus.NOT_LOAD
      }

      // 加载资源
      function load() {
        return new Promise<void>((resolve, reject) => {
          const nv = props.src
          if (nv instanceof File) {
            fileRead(nv, 'base64').then((base64) => {
              audioElem.value.src = base64
              loaded.value = true
              resolve()
            }, reject)
          } else {
            if (nv) {
              audioElem.value.src = qiniuFullLink(nv)
              loaded.value = true
            }
            resolve()
          }
        })
      }

      onMounted(() => {
        if (!props.lazy) load()
      })

      watch(
        () => props.src,
        () => {
          unload()
          if (!props.lazy) load()
        }
      )

      // audio 播放时间更新
      function onTimeupdate(e: Event) {
        if (mousedown) return
        const target = e.target as HTMLMediaElement
        if (target.duration && target.currentTime) {
          const val = target.currentTime / target.duration
          played.value = isNaN(val) ? 0 : (val * 100).toFixed(2)
        } else {
          played.value = 0
        }
      }

      // audio 播放结束
      function onEnded() {
        status.value = AudioStatus.PAUSED
        played.value = 0
      }

      // audio 资源加载失败
      function onError() {
        // 进行 <audio>.src = '' 式赋值时也会报错
        if (audioElem.value.getAttribute('src')) {
          status.value = AudioStatus.ERROR
        }
        played.value = 0
      }

      // audio 资源加载到足以进行播放
      function onCanplay() {
        // 拖拽进度条时也会触发 oncanplay 事件
        if (status.value !== AudioStatus.PAUSED && status.value !== AudioStatus.PLAYED) {
          status.value = AudioStatus.PAUSED
        }
      }

      /** 拖拽进度条 */
      function onMousedown(e: MouseEvent) {
        mousedown = true
        const { left, width } = progressElem.value.getBoundingClientRect()

        function setPlayed(x: number, l: number, w: number) {
          played.value = (((x - l) / w) * 100).toFixed(2)
          if (audioElem.value.paused) {
            onPlay()
          }
        }

        setPlayed(e.clientX, left, width)

        const mousemove = (ev: MouseEvent) => {
          setPlayed(ev.clientX, left, width)
        }

        const mouseup = () => {
          mousedown = false
          const currentTime = (Number(played.value) / 100) * audioElem.value.duration
          if (!isNaN(currentTime)) {
            audioElem.value.currentTime = currentTime
          }
          document.removeEventListener('mousemove', mousemove)
          document.removeEventListener('mouseup', mouseup)
        }

        document.addEventListener('mousemove', mousemove)
        document.addEventListener('mouseup', mouseup)
      }

      /** 播放/暂停 */
      function onPlay() {
        if (!props.src) return

        const elem = unref(audioElem)

        // 暂停状态
        if (elem.paused) {
          // 有资源。https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLMediaElement/readyState
          if (elem.readyState >= 2) {
            elem.play() // 播放
            status.value = AudioStatus.PLAYED
          } else {
            if (!loaded.value && props.src) {
              // 加载资源
              load().then(() => {
                if (loaded.value) {
                  function play() {
                    if (!elem.getAttribute('src') || status.value === AudioStatus.ERROR) return
                    if (status.value === AudioStatus.PAUSED) {
                      elem.play() // 播放
                      status.value = AudioStatus.PLAYED
                    } else {
                      requestAnimationFrame(play)
                    }
                  }
                  play()
                }
              })
            }
          }
        } else {
          elem.pause() // 暂停
          status.value = AudioStatus.PAUSED
        }
      }

      return {
        status,
        played,
        imgErrorSize,
        audioElem,
        progressElem,
        boxStyle,
        iconName,
        iconCursor,
        iconColor,
        loaded,
        onTimeupdate,
        onEnded,
        onError,
        onCanplay,
        onMousedown,
        onPlay,
      }
    },
  })
</script>
